# Copyright (c) 2011 Phillip Nguyen.
# All rights reserved.

"""
This module replaces pyglet's sprite class.
It uses an extension written in C to speed up sprite
rendering and more intelligently handles animations.
"""

__docformat__ = 'plaintext'


import imp, os, platform

# Determine working directory so we can find shared library.
path = os.path.dirname(__file__)

# Load the csprite module contained in shared library
s = platform.system()
if s in ('Linux', 'FreeBSD'):
    _fastsprite = imp.load_dynamic('_fastsprite', os.path.join(path, '_fastsprite-linux.so'))
elif s == 'Darwin':
    _fastsprite = imp.load_dynamic('_fastsprite', os.path.join(path, '_fastsprite-darwin.so'))
elif s == 'Windows':
    _fastsprite = imp.load_dynamic('_fastsprite', os.path.join(path, '_fastsprite-windows.pyd'))
	
from pyglet import gl 
        
# does nothing by itself
class SpriteFrame(object):
    def __init__(self, image, duration):
        self.texture = image.get_texture()
        self.duration = duration


class Sprite(object):
    def __init__(self, image_or_framelist, x=0, y=0):
        """A Sprite object may be initialized with either a pyglet image or texture instance or
        with a list of SpriteFrames if the sprite is animated.  You can also provide an initial 
        position for the sprite."""

        # Initialize underlying csprite object. The csprite shape and texture coordinates
        # get set when assigning the texture with set_texture.
        self._csprite = _fastsprite.Sprite()
        self._csprite.x = x
        self._csprite.y = y

        if isinstance(image_or_framelist, list) or isinstance(image_or_framelist, tuple):
            self.frames = image_or_framelist
            self.frame_counter = 0
            self.texture = self.frames[self.frame_counter].texture
            self.timer = 0
            self.is_animated = True
            # If the last frame in the list is 0 or None, then this indicates that
            # the animation does not loop.
            if not self.frames[-1]:
                self.is_looping = False
                self.frames = self.frames[:-1]
            else:
                self.is_looping = True
        else:
            self.texture = image_or_framelist.get_texture()
            self.is_animated = False

        self._initialize_texture_parameters()

    def set_texture(self, texture):
        self._texture = texture
        # After assigning a new texture, we must also reset the size
        # and texture coordinates of the sprite.
        self._csprite.texture_id = texture.id
        self._csprite.x1 = -texture.anchor_x
        self._csprite.y1 = -texture.anchor_y
        self._csprite.x2 = self._csprite.x1 + texture.width
        self._csprite.y2 = self._csprite.y1 + texture.height
        u1, v1, r1, u2, v2, r2, u3, v3, r3, u4, v4, r4 = texture.tex_coords
        self._csprite.u1 = u1
        self._csprite.u2 = u2
        self._csprite.v1 = v1
        self._csprite.v2 = v3

    texture = property(lambda self: self._texture, set_texture)

    def advance_frame(self):
        if not self.is_animated: return False

        self.frame_counter += 1
        if self.frame_counter >= len(self.frames):
            if self.is_looping:
                self.frame_counter = 0
            else:
                self.frame_counter -= 1
                self.is_animated = False
                return False
            
        self.texture = self.frames[self.frame_counter].texture
        return True

    def reset(self):
        if hasattr(self, 'frames'):
            self.frame_counter = 0
            self.texture = self.frames[self.frame_counter].texture
            self.timer = 0
            self.is_animated = True
    
    def update(self, dt):
        if not self.is_animated: return False
        
        self.timer += dt
        if self.timer >= self.frames[self.frame_counter].duration:
            self.timer = 0            
            return self.advance_frame()
        
        return True
    
    def bounding_box(self):
        """Returns a scaled bounding box for the sprite, but does not
        take into account rotation."""
        x1 = self._csprite.x + self.scale * self._csprite.x1
        y1 = self._csprite.y + self.scale * self._csprite.y1
        x2 = self._csprite.x + self.scale * self._csprite.x2
        y2 = self._csprite.y + self.scale * self._csprite.y2
        return (x1, y1, x2-x1, y2-y1)

    def hit(self, x, y):
        if x < self._csprite.x + self.scale * self._csprite.x1:
            return False
        if y < self._csprite.y + self.scale * self._csprite.y1:
            return False
        if x > self._csprite.x + self.scale * self._csprite.x2:
            return False
        if y > self._csprite.y + self.scale * self._csprite.y2:
            return False
        return True
        
    def set_x(self, x):
        self._csprite.x = x

    def set_y(self, y):
        self._csprite.y = y
        
    x = property(lambda self: self._csprite.x, set_x)
    y = property(lambda self: self._csprite.y, set_y)

    def set_position(self, position):
        self._csprite.x = position.x
        self._csprite.y = position.y

    position = property(lambda self: (self._csprite.x, self._csprite.y), set_position)

    width = property(lambda self: (self._csprite.x2 - self._csprite.x1)*self.scale)
    height = property(lambda self: (self._csprite.y2 - self._csprite.y1)*self.scale)

    def set_rotation(self, rot):
        self._csprite.rot = rot
        
    def set_scale(self, scale):
        self._csprite.scale = scale
        
    rotation = property(lambda self: self._csprite.rot, set_rotation)
    scale = property(lambda self: self._csprite.scale, set_scale)

    def _initialize_texture_parameters(self):
        """Bind each texture used by the sprite and preset its texture parameters,
        specifically the texture wrapping mode."""
        if self.is_animated:
            texture_list = [frame.texture for frame in self.frames]
        else:
            texture_list = [self.texture]

        for texture in texture_list:
            gl.glEnable(texture.target)
            gl.glBindTexture(texture.target, texture.id)
            gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
            gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
    
    def draw(self):
        # This method assumes that the following commands have already been
        # issued before it was called.
        # glEnable(GL_TEXTURE_2D) 
        # glEnable(GL_BLEND)
        # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        self._csprite.draw()

    def draw_scaled(self, scale):
        # This method assumes that the following commands have already been
        # issued before it was called.
        # glEnable(GL_TEXTURE_2D) 
        # glEnable(GL_BLEND)
        # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        orig_scale = self._csprite.scale
        self._csprite.scale = scale
        self._csprite.draw()
        self._csprite.scale = orig_scale

__all__ = ['Sprite', 'SpriteFrame']        
